package com.wuhong.filter;



import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

import com.wuhong.entity.Accounts;
import com.wuhong.utils.JWTUtil;
import io.netty.buffer.ByteBufAllocator;

import lombok.extern.slf4j.Slf4j;



import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.cloud.gateway.support.DefaultServerRequest;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;

import org.springframework.core.io.buffer.*;

import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;

import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;

import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;


import java.io.*;

import java.util.List;

/**
 * @ClassName DiaryFilter
 * @Description TODO
 * @Author director吴
 * @Date 2022/1/16 21:14
 * @Version 1.0
 **/
@Slf4j
@Configuration
public class DiaryFilter implements GlobalFilter, Ordered {


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        //从请求头获取Token
        String token = exchange.getRequest().getHeaders().get("token").get(0);

        if (token != null) {
            boolean status = JWTUtil.checkToken(token);
            //验证解析token
            Accounts info = JWTUtil.checkToken(token) ? JWTUtil.getTokenInfo(token) : null;
            ServerHttpRequest request = exchange.getRequest();

            //获取http请求头相关信息
            HttpHeaders headers = request.getHeaders();
            MediaType contentType = headers.getContentType();
            long contentLength = headers.getContentLength();

            //判断请求类型
            if (contentLength > 0) {
                //对application/json请求格式的处理
                if (MediaType.APPLICATION_JSON.equals(contentType) || MediaType.APPLICATION_JSON_UTF8.equals(contentType)) {
                    return readJSONBody(exchange, chain, info);
                }
//                if (MediaType.APPLICATION_FORM_URLENCODED.equals(contentType)) {
//                    return readFormData(exchange, chain,gatewayContext);
//                }
                //对form/data请求格式的处理
                if (MediaType.MULTIPART_FORM_DATA.getSubtype().equals(contentType.getSubtype())) {
                    return readFormData(exchange, chain,info);
                }
            }

            return chain.filter(exchange);

        } else {

            String value = exchange.getRequest().getPath().value();

            if (value.indexOf("login") != -1) {
                return chain.filter(exchange);
            }

        }
        return null;
    }




    /*
    返回一个ServerHttpRequestDecorator对象。
    这个对象是用来构建一个request请求的适配。因为gateway使用webflux,流节点中（Mono、Flux）当request的数据被读取后，
    就不会在原来的节点存在。通俗来讲"就是拿出来用了，就没了。需要继续往下传递，必须再写一个数据放进去"
     */
    public ServerHttpRequestDecorator buildingServerHttpRequestDecorator(ServerWebExchange exchange,byte[] bytes){
        return new ServerHttpRequestDecorator(exchange.getRequest()) {

            //请求头配置
            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.putAll(super.getHeaders());
                httpHeaders.setContentLength(bytes.length);
                return httpHeaders;
            }

            //请求体，body数据配置
            @Override
            public Flux<DataBuffer> getBody() {
                Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
                    DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
                    DataBufferUtils.retain(buffer);
                    return Mono.just(buffer);
                });

                return cachedFlux;
            }
        };
    }



    /**
     * 暂未解决的问题：使用form-data-上传文件时，通过修改http请求体，byte转string修改，再转回byte,除了txt文件意外，其他文件都会损坏
     */
    private Mono<Void> readFormData(ServerWebExchange exchange, GatewayFilterChain chain,Accounts tokenInfo) {

        return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer -> {
            StringBuilder builder = new StringBuilder();
            byte[] bytes = new byte[dataBuffer.readableByteCount()];
            log.info("dataBuffer数据长度==" + dataBuffer.readableByteCount());
            try {
                //读取-响应式流结构中,http-from-data的请求数据
                dataBuffer.read(bytes);
                String bodyInfo = new String(bytes);
                System.out.println(bodyInfo);

                //判断-from-data的请求内容，是否携带了文件。携带了文件的话 body请求体中，会有filename参数
                if (bodyInfo.contains("filename=")){
                    ServerHttpRequestDecorator decorator = buildingServerHttpRequestDecorator(exchange, bytes);
                    return chain.filter(exchange.mutate().request(decorator).build());
                }

                //插入
                String newBody = insertBodyForFromData(bodyInfo, tokenInfo);

                byte[] overByte = newBody.getBytes();

                ServerHttpRequestDecorator decorator = buildingServerHttpRequestDecorator(exchange, overByte);



                return chain.filter(exchange.mutate().request(decorator).build());

            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        });


    }

    private Mono<Void> readJSONBody(ServerWebExchange exchange, GatewayFilterChain chain, Accounts tokenInfo) {


        // httpserverRequest 转 ServerRequest
        ServerRequest serverRequest = new DefaultServerRequest(exchange);

        Mono<String> modifiedBody = serverRequest.bodyToMono(String.class)
                .flatMap(body -> {//请求中的body数据
                    log.info("'[请求中的body数据]':\n" + body);

                    return Mono.just(insertBodyForJSON(body, tokenInfo));
                });
        BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
        HttpHeaders headers = new HttpHeaders();
        headers.putAll(exchange.getRequest().getHeaders());
        headers.remove(HttpHeaders.CONTENT_LENGTH);
        CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);

        return bodyInserter.insert(outputMessage, new BodyInserterContext())
                .then(Mono.defer(() -> {
                    ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(
                            exchange.getRequest()) {
                        @Override
                        public HttpHeaders getHeaders() {
                            long contentLength = headers.getContentLength();
                            HttpHeaders httpHeaders = new HttpHeaders();
                            httpHeaders.putAll(super.getHeaders());
                            if (contentLength > 0) {
                                httpHeaders.setContentLength(contentLength);
                            } else {
                                httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
                            }
                            return httpHeaders;
                        }

                        @Override
                        public Flux<DataBuffer> getBody() {
                            return outputMessage.getBody();
                        }
                    };
                    return chain.filter(exchange.mutate().request(decorator).build());
                }));
    }


    private String insertBodyForJSON(String oldBody, Accounts insertUserData) {
        JSONObject jsonObject = JSON.parseObject(oldBody);
        jsonObject.put("userId", insertUserData.getId());
        log.info("[gatway添加用户Id信息] :[id=" + insertUserData.getId() + "]");
        jsonObject.put("userAccount", insertUserData.getAccount());
        log.info("[gatway添加用户账号信息] :[account=" + insertUserData.getAccount() + "]");
        return jsonObject.toJSONString();

    }

    private String insertBodyForFromData(String oldBody, Accounts insertUserData) {
        String boundary = oldBody.split("Content-Disposition: form-data;")[0];
        StringBuilder builder = new StringBuilder();
        log.info("请求格式为form-data");
        //http form-data报文格式，需要添加\r\n
        builder.append(boundary + "Content-Disposition: form-data; name=\"userId\"\r\n" +
                "\r\n" +
                "wuhong\r\n");
//                builder.append(boundary + "Content-Disposition: form-data; name=\"userId\"" +
//                        "\n" +
//                        "\n" +
//                        "wuhong\n");
        builder.append(oldBody);
        log.info("修改以后的数据");
        System.out.println(builder.toString());
        return builder.toString();

    }


    private byte[] insertBodyFromData(DataBuffer dataBuffer) {
        InputStream inputStream = dataBuffer.asInputStream();
        StringBuilder builder = new StringBuilder();
        byte[] bytes = new byte[dataBuffer.readableByteCount()];
        System.out.println("dataBuffer数据长度==" + dataBuffer.readableByteCount());
        try {
            dataBuffer.read(bytes);
            String bodyInfo = new String(bytes);
            System.out.println(bodyInfo);
            String formDataInfo = bodyInfo.split("Content-Disposition: form-data;")[0];
            builder.append(formDataInfo + "Content-Disposition: form-data; name=\"userId\"" +
                    "\n" +
                    "\n" +
                    "wuhong\n");
            builder.append(bodyInfo);
            System.out.println(builder.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
        String newBody = builder.toString();
        byte[] overByte = newBody.getBytes();

        return overByte;
    }


    @Override
    public int getOrder() {
        return 1;
    }


}
